﻿using System;
using System.Diagnostics;
using System.Threading;

namespace PKTools.Scheduler
{
    class TaskWorker
    {
        readonly int
            id;
        readonly ITaskDispatcher
            dispatcher;
        readonly ManualResetEvent
            stopEvent;
        readonly TaskExecutorConfig
            config;

        public readonly Thread
            Thread;

        public TaskWorker(int id, ITaskDispatcher dispatcher, ManualResetEvent stopEvent, TaskExecutorConfig config)
        {
            this.id = id;
            this.dispatcher = dispatcher;            
            this.stopEvent = stopEvent;
            this.config = config;

            this.Thread = new Thread(Worker);
            this.Thread.Start();
        }

        void Worker()
        {
            Log.Entry(LogSeverity.Info, "worker {0} start", this.id);

            ITask
                task = null;

            try
            {
                for (; ; )
                {
                    var
                        currentTime = DateTime.UtcNow;

                    lock (this.dispatcher)
                    {
                        using (new Stoper("get task", () => task == null ? string.Empty : task.ToString()))
                            task = this.dispatcher.GetWaitingTask(currentTime);
                    }

                    bool
                        wait = true,
                        success = false;

                    if (task != null)
                    {
                        Log.Entry(LogSeverity.Info, "worker {0} executing task {1}", this.id, task);

                        // before execution
                        using (var sql = this.config.SqlCreate(true))
                        {
                            var
                                job = task.GetJobStep().GetJob();

                            job.BeforeExecution(currentTime);
                            task.BeforeExecution(currentTime);

                            sql.Commit();
                        }

                        // execution
                        try
                        {
                            using (new Stoper("execute task " + task))
                            {
                                task.Execute();
                            }

                            success = true;
                        }
                        catch (Exception ex)
                        {
                            // failure
                            using (var sql = this.config.SqlCreate(true))
                            {
                                Log.Entry(LogSeverity.Info, "worker {0} task {1} fail, {2}", this.id, task, ex.Message);
                                task.Fail(currentTime, ex);

                                if (task.CanRetry())
                                {
                                    Log.Entry(LogSeverity.Info, "worker {0} task {1} retry", this.id, task, ex.Message);
                                    task.Retry(ex);
                                }
                                else
                                {
                                    Log.Entry(LogSeverity.Info, "worker {0} task {1} final fail", this.id, task, ex.Message);
                                    task.FinalFail(ex);
                                    task.DeleteTask();

                                    wait = AfterTaskExecuted(task, currentTime, ex);
                                }

                                sql.Commit();
                            }
                        }
                        finally
                        {
                            if (success)
                            {
                                // success
                                using (var sql = this.config.SqlCreate(true))
                                {
                                    task.Success(currentTime);
                                    task.DeleteTask();

                                    wait = AfterTaskExecuted(task, currentTime, null);

                                    sql.Commit();
                                }
                            }
                        }
                    }

                    if (wait && this.stopEvent.WaitOne(1000 * this.config.WorkerSleep, true))
                    {
                        Log.Entry(LogSeverity.Debug, "worker {0} stop", this.id);
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Entry(LogSeverity.Error, "worker {0} exception {1}", this.id, ex.Message);

                Debug.Assert(true, ex.Message);
            }
        }

        bool AfterTaskExecuted(ITask task, DateTime currentTime, Exception ex)
        {
            var
                currentStep = task.GetJobStep();
            var
                job = currentStep.GetJob();

            if (currentStep.GetStopJob() && ex != null)
            {
                // if failure and stop job
                job.Stop();
                return true;
            }

            var
                nextStep = currentStep.GetNextStep();

            if (nextStep == null)
            {
                // that was last step
                if (job.Schedule(currentTime))  // schedule next run
                {
                    // create task for first job step
                    job.GetFirstStep().CreateTask();
                }

                return true;
            }

            // create task for next step
            nextStep.CreateTask();
            return false;
        }
    }
}
